package ecmnext.agent;
import ecmnext.utils.*;
import java.util.logging.*;
import java.util.*;
import org.apache.log4j.*;
import org.apache.log4j.Logger;
import ecmnext.client.event.*;
import ecmnext.client.*;
import org.jdom.*;
import ecmnext.net.*;
import java.io.IOException;

/**
 * <p>Title: ECMNext Agent</p>
 * <p>Description: Cette classe contient le comportement de l'agent. Les
 * primitives contenant portefeuille, etc... étant deja gardées au niveau
 * de ClientCore.</p>
 * <p>Copyright: Copyright (c) 2011</p>
 * <p>Company: </p>
 * @author Xuewen YANG ET Antoine COUDERC
 * @version 0.1
 */

public abstract class Agent extends Thread
    implements
    ExperimentDeveloppmentListener,
    NetworkListener {

  private final static int CALL_EXPERIMENT_BEGIN = 0;
  private final static int CALL_PERIOD_BEGIN = 1;
  private final static int CALL_EXPERIMENT_END = 2;
  private final static int CALL_PERIOD_END = 3;
  private final static int CALL_EXP_OFF = 4;
  private final static int CALL_EXP_ON_PER_OFF = 5;
  private final static int CALL_EXP_ON_PER_ON = 6;
  private final static int CALL_CLIENT_READY = 7;

  /**
   * Ces deux champs gère les prorpiétés reglables en externe de l'agent.
   * Le premier specifie le fichier ou les trouver, le deuxieme contient
   * les proprietes apres leur chargement (en meme temps que la classe).
   */
  private static final String AGENT_PROPERTIES_FILES = "agent.properties";
  public static Properties agentProperties = new Properties();

  /**
   * Ce thread sera lance des sa creation jusqu'a l'arret final de l'apps.
   * Cette variable est utilisé uniquement pour savoir si le corps de la boucle
   * sans fin dans la methode run() doit etre execute ou non.
   * stopAgent et startAgent servent à modifier le champ.
   */
  private boolean execState = false;
  /** @see execState */
  public final void stopAgent() {this.execState = false;}
  /** @see execState */
  public final void startAgent() {this.execState = true;}

  private AgentTaskQueue taskQueue = new AgentTaskQueue();

  /**
   * The time (in ms) the agent will pause before evalutating another time
   * it's options.
   */
  protected long pauseDuration = 1000;

  /**
   * Ce bloc static est appelé uniquement lors de LA premiere référence la
   * classe. Jamais il ne sera rappelée plus tard.
   * Il sert a initialiser les propriétés de l'agent.
   */
  static {
    ECMAgentApps.getLogger().debug("Loading agent properties...");
    String path = System.getProperty("user.dir") +
        System.getProperty("file.separator") + AGENT_PROPERTIES_FILES;
    Utils.loadApplicationProperties(path, agentProperties);

    try {
      String host = agentProperties.getProperty("host");
      String user = agentProperties.getProperty("login");
      String pass = agentProperties.getProperty("password");
      ClientCore.connecToServer(host, user + (int)(Math.random()*10000), pass);
    }
    catch (IOException ex) {
      System.out.println(ex.getMessage());
    }

  }

  public Agent(long pauseDuration) {
    this.pauseDuration = pauseDuration;
    ClientCore.getExperimentManager().addListener(this);
    ClientCore.addNetworkListener(this, "ExperimentUpdate");
    this.start();
  }

  private static int getExperimentStage() {
    return ClientCore.getExperimentManager().getExperimentState();
  }

  /**
   * Ces méthodes sont appelées lorsque l'expérience change d'état.
   * Elles sont finales pour ne pas perdre la synchronisation entre toutes
   * les tâhes pouvant être appelés au niveau du serveur.
   */
  public final void experimentBegins() {
    //JesseAgentApps.getLogger().info("Experiment beginning");
    this.taskQueue.addTask(CALL_EXPERIMENT_BEGIN);
  }

  public final void experimentFinished() {
    //JesseAgentApps.getLogger().info("Experiment ending");
    this.taskQueue.addTask(CALL_EXPERIMENT_END);
  }

  public final void periodBegins() {
    //JesseAgentApps.getLogger().info("period beginning");
    this.taskQueue.addTask(CALL_PERIOD_BEGIN);
  }

  public final void periodFinished() {
    //JesseAgentApps.getLogger().info("period ending");
    this.taskQueue.addTask(CALL_PERIOD_END);
  }

  /**
   * A chaque nouvelle etape du deroulement de l'experience, le serveur envoie
   * un message d'update. Ils sont tous traité par défaut dans le gestionnaire
   * d'expérience de ecmnext.client, sauf un. Celui qui demande si le client est
   * pret.
   * Cette méthode ne traitera donc que ce cas, pour ne pas interférer avec
   * le traitement du gestionnaire.
   * @param xmlObject Document
   */
  public void objectReceived(Document xmlObject) {
    //JesseAgentApps.getLogger().debug("experiment update received...");
    ExpUpdate update = new ExpUpdate(ExpUpdate.EXPERIMENT_BEGINNING, "", 0);

    // on ne traite l'objet que s'il s'initialise correctement
    if (update.initFromNetworkInput(xmlObject.getRootElement())) {
      if (update.getUpdateType() == ExpUpdate.CLIENT_READY) {
        //JesseAgentApps.getLogger().info("it's of type CLIENT_READY... " +
        //                                "adding it to task queue");
        // On ajoute le fait de notifier le serveur que le client est pret ?la
        // file d'attente des taches. Cela permettra de terminer les tache en
        // attente avant de repondre que le client est pret.
        this.taskQueue.addTask(CALL_CLIENT_READY);
      }
    }
  }

  protected final void notifyClientReady() {
    ClientCore.send(new ExpUpdate(ExpUpdate.CLIENT_READY, "", -1));
  }

  /**
   * overload these methods to control the behavior of the agent.
   * A call back to these, will keep functionnality implemented here.
   */
  protected void experimentStarting() {
    this.startAgent();
  }

  protected void experimentEnding() {
    this.stopAgent();
  }

  protected abstract void periodStarting();
  protected abstract void periodEnding();

  /**
   * Cette methode sera appele par la boucle d'execution(chaque
   * seconde jusqu'a ce que l'etat de l'experience change)  si l'experience
   * n'a pas encore ete lancee.
   */
  protected abstract void runExperimentOff();

  /**
   * Cette methode sera appelee par la boucle d'execution sans fin (chaque
   * seconde jusqu'a ce que l'etat de l'experience change) si l'experience
   * a ete lance, et entre deux periodes.
   */
  protected abstract void runExperimentOnPeriodOff();

  /**
   * Cette methode sera appelee par la boucle d execution sans fin (chaque
   * seconde jusqu'a ce qye l'etat de l'experience change) si l'experience
   * a ete lance, et au curs d'une periode de jeu.
   */
  protected abstract void runExperimentOnPeriodOn();

  public final void run() {
    while(true) {
      // le corps de la boucle est execut?uniquement si le champ d'etat
      // d'execution est a vrai.
      //JesseAgentApps.getLogger().info("agent running : " + execState);
      if (execState) {

        switch(getExperimentStage()) {
          case ClientExperimentManager.EXP_OFF :
            this.taskQueue.addTask(CALL_EXP_OFF);
            break;
          case ClientExperimentManager.EXP_ON_PER_OFF :
            this.taskQueue.addTask(CALL_EXP_ON_PER_OFF);
            break;
          case ClientExperimentManager.EXP_ON_PER_ON :
            this.taskQueue.addTask(CALL_EXP_ON_PER_ON);
            break;
          default :
            ECMAgentApps.getLogger().warn("Unexpected experiment state (state " +
                                            getExperimentStage() + "). If a " +
                                            "state has been added, you should update " +
                                            "the agent code so that it supports this " +
                                            "new state. Ignoring this state.");
        }
      }

      // on n'essaye de faire quelque chose qu'une fois par seconde.
      try {
        if (this.pauseDuration>0)
        sleep(this.pauseDuration);
      }
      catch (InterruptedException ex) {
        ECMAgentApps.getLogger().warn(ex.getMessage());
      }
    }
  }

  /**
   * <p>Title: AgentTaskQueue</p>
   * <p>Description: Cette classe execute les actions de manière sequentielle.
   * Elle permet ainsi à deux Thread de ne pas executer en meme temps deux
   * méthodes différentes.</p>
   * <p>Copyright: Copyright (c) 2011</p>
   * <p>Company: </p>
   * @author Xuewen YANG ET Antoine COUDERC
   * @version 0.1
   */
  class AgentTaskQueue extends Thread {
    private Vector tasks = new Vector();

    public AgentTaskQueue() {
      this.start();
    }

    public void addTask(int task) {
      // on insere la tache a la fin de la liste
      //JesseAgentApps.getLogger().info("adding task...");
      this.tasks.add(new Integer(task));
    }

    private void treatTask() {
      // liste des taches vide ?
      if (tasks.size() <= 0) return;

      // on recupere la premiere tache insérés (fifo)
      int task = ((Integer)this.tasks.elementAt(0)).intValue();
      tasks.remove(0);

      switch(task) {
        case CALL_EXPERIMENT_BEGIN :
          experimentStarting();
          break;
        case CALL_EXPERIMENT_END :
          experimentEnding();
          break;
        case CALL_EXP_OFF :
          runExperimentOff();
          break;
        case CALL_EXP_ON_PER_OFF :
          runExperimentOnPeriodOff();
          break;
        case CALL_EXP_ON_PER_ON :
          runExperimentOnPeriodOn();
          break;
        case CALL_PERIOD_BEGIN :
          periodStarting();
          break;
        case CALL_PERIOD_END :
          periodEnding();
          break;
        case CALL_CLIENT_READY :
          notifyClientReady();
          break;
        default :
          ECMAgentApps.getLogger().warn("Unexpected task (task " + task
                                          + "). If a task has been added, " +
                                          "you should update the agent code " +
                                          "so that it supports this " +
                                          "new task. Ignoring this task.");
      }
    }

    public void run() {
      while(true) {
        if (tasks.size() > 0) {
          treatTask();
        } else {
          try {
            sleep(500);
          }
          catch (InterruptedException ex) {
            ECMAgentApps.getLogger().warn(ex.getMessage());
          }
        }
      }
    }
  }
}


